﻿
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using Xunit.Extensions;

namespace EmperialApps.WeatherSpark.Data {

    public class TestItemManager {

        [Fact]
        public void SetItemValue_CalledBeforeGetItemSize( ) {
            var expected = new[] { "SetItemValue", "GetItemSize" };
            var manager = new MockItemManager( );

            manager.CallDisplayItems( S, 1 + Maximum, 0, ScreenSize );
            var actual = manager.Calls.Intersect( expected ).ToArray( );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void DisplayItems_GivenFullRange_CreatesAllItems( ) {
            double offset = 0;
            double increment = Maximum / 2;
            var expectedValues = S.GetRangeValues( increment );
            var expectedItems = GetExpectedItems( S, offset, expectedValues );
            var manager = new MockItemManager( );

            var actualValues = manager.CallDisplayItems( S, increment, offset, ScreenSize );
            var actualItems = manager.Items;

            Assert.Equal( expectedValues, actualValues );
            Assert.Equal( expectedItems, actualItems );
        }

        [Fact]
        public void DisplayItems_GivenPartialRange_CreatesSubsetOfItems( ) {
            double offset = ScreenSize / 4;
            double increment = Maximum / 4;
            var expectedValues = S.GetRangeValues( increment );
            var expectedItems = GetExpectedItems( S, offset, expectedValues.Where( v => v >= increment ) );
            var manager = new MockItemManager( );

            var actualValues = manager.CallDisplayItems( S, increment, offset, ScreenSize );
            var actualItems = manager.Items;

            Assert.Equal( expectedValues, actualValues );
            Assert.Equal( expectedItems, actualItems );
        }

        [Fact]
        public void DisplayItems_GivenPartialRange_WithOverhangAllowance_IncludesOutOfRangeItems( ) {
            double allowance = 2 * ItemSize;
            double offset = ScreenSize / 4 + allowance - 1;
            double increment = Maximum / 4;
            var expectedValues = S.GetRangeValues( increment );
            var expectedItems = GetExpectedItems( S, offset, expectedValues.Where( v => v >= increment ) );
            var manager = new MockItemManager( allowance );

            var actualValues = manager.CallDisplayItems( S, increment, offset, ScreenSize );
            var actualItems = manager.Items;

            Assert.Equal( expectedValues, actualValues );
            Assert.Equal( expectedItems, actualItems );
        }

        [Fact]
        public void DisplayItems_WhenRangeDecreased_HidesExtraItems( ) {
            double offset = ScreenSize / 2;
            double increment = Maximum / 4;
            var expectedValues = S.GetRangeValues( increment );
            var expectedVisibleItems = GetExpectedItems( S, offset, expectedValues.Where( v => v >= 2 * increment ) );
            int expectedHiddenItems = expectedValues.Count( ) - expectedVisibleItems.Length;
            var manager = new MockItemManager( );

            manager.CallDisplayItems( S, increment, 0, ScreenSize );
            var actualValues = manager.CallDisplayItems( S, increment, offset, ScreenSize );
            var actualVisibleItems = manager.Items.Where( i => i.Visible );
            var actualHiddenItems = manager.Items.Count( i => !i.Visible );

            Assert.Equal( expectedValues, actualValues );
            Assert.Equal( expectedVisibleItems, actualVisibleItems );
            Assert.Equal( expectedHiddenItems, actualHiddenItems );
        }

        [Theory]
        [InlineData( 0, ScreenSize / 4 )]
        [InlineData( ScreenSize / 2, 0 )]
        public void DisplayItems_WhenRangeChanged_ReusesExistingValueItems( double firstOffset, double secondOffset ) {
            double increment = Maximum / 4;
            double displayLimit = ScreenSize / 2;
            var manager = new MockItemManager( );

            manager.CallDisplayItems( S, increment, firstOffset, displayLimit );
            var originalValues = manager.Items.ToDictionary( i => i.Value );

            manager.CallDisplayItems( S, increment, secondOffset, displayLimit );
            var actualItems = manager.Items.Where( i => i.Visible && originalValues.ContainsKey( i.Value ) ).ToArray( );

            foreach( var item in actualItems ) {
                int expectedId = originalValues[item.Value].Id;
                int actualId = item.Id;
                Assert.Equal( expectedId, actualId );
            }
        }

        [Fact]
        public void DisplayItems_WhenIntervalChanged_DoesNotDisplayOldValues( ) {
            double firstIncrement = Maximum / 4;
            double secondIncrement = Maximum / 5;
            double displayLimit = ScreenSize / 2;
            var manager = new MockItemManager( );

            var originalValues = manager.CallDisplayItems( S, firstIncrement, 0, displayLimit );

            var actualValues = manager.CallDisplayItems( S, secondIncrement, 0, displayLimit );

            var oldValues = originalValues.Except( actualValues ).ToArray( );
            var oldItems = manager.Items.Where( i => i.Visible && oldValues.Contains( i.Value ) );

            Assert.Empty( oldItems );
        }


        #region Utility

        private const double Minimum = 0;
        private const double Maximum = 100;
        private const double ItemSize = 50;
        private const double ItemOffset = ItemSize / 2;
        private const double ScreenSize = 1000;

        private static readonly Scale S = new Scale( false, ScreenSize, new double[] { Minimum, Maximum } );

        private static MockItem[] GetExpectedItems( Scale scale, double offset, IEnumerable<double> expectedValues ) {
            var expectedItems = expectedValues.Select( value => new MockItem( scale, value, offset ) ).ToArray( );
            return expectedItems;
        }

        private sealed class MockItem : IEquatable<MockItem> {
            private static int _nextId;
            public readonly int Id;
            public bool Visible;
            public double Value;
            public double Position;

            public MockItem( Scale scale, double value, double offset )
                : this( value, scale.ToScreen( value ) - offset - ItemOffset ) { }

            public MockItem( double value = 0, double position = 0, bool visible = true ) {
                this.Id = _nextId++;
                this.Visible = visible;
                this.Value = value;
                this.Position = position;
            }

            public bool Equals( MockItem other ) {
                return other != null
                    && other.Visible == this.Visible
                    && other.Value == this.Value
                    && other.Position == this.Position;
            }

            public override bool Equals( object obj ) {
                return this.Equals( obj as MockItem );
            }

            public override int GetHashCode( ) {
                return 0;
            }

            public override string ToString( ) {
                return string.Format( "Value={0}, Position={1}, Visible={2}", this.Value, this.Position, this.Visible );
            }
        }

        private sealed class MockItemManager : ItemManager<MockItem> {
            public readonly List<MockItem> Items = new List<MockItem>( );
            public readonly List<string> Calls = new List<string>( );

            public MockItemManager( double overhangAllowance = 0 )
                : base( overhangAllowance ) { }

            public IList<double> CallDisplayItems( Scale scale, double increment, double offset, double displayLimit ) {
                return DisplayItems( scale, increment, offset, displayLimit );
            }

            protected override MockItem CreateItem( ) {
                Calls.Add( "CreateItem" );
                var item = new MockItem( );
                this.Items.Add( item );
                return item;
            }

            protected override void SetItemPosition( MockItem item, double position ) {
                Calls.Add( "SetItemPosition" );
                item.Position = position;
            }

            protected override void SetItemVisibility( MockItem item, bool visible ) {
                Calls.Add( "SetItemVisibility" );
                item.Visible = visible;
            }

            protected override void SetItemValue( MockItem item, double value ) {
                Calls.Add( "SetItemValue" );
                item.Value = value;
            }

            protected override double GetItemValue( MockItem item ) {
                Calls.Add( "GetItemValue" );
                return item.Value;
            }

            protected override double GetItemSize( MockItem item ) {
                Calls.Add( "GetItemSize" );
                return ItemSize;
            }
        }

        #endregion
    }

}
